﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DistributedGraph.FloodSet
{
    /// <summary>
    /// FloodSet spezifischer Knoten
    /// </summary>
    public class Node : DistributedGraph.Node
    {
        #region Typen
        public delegate void MsgEvent(Node sender, Node receiver, Link link, Message message);
        public delegate void SimpleEvent(Node node);
        #endregion Typen

        #region Attribute
        private int value;
        protected HashSet<int> valueSetBefore = new HashSet<int>();
        private HashSet<int> valueSetAfter = new HashSet<int>();
        protected int round;
        private Dictionary<string, HashSet<int>> valuesPerRound;
        private Dictionary<string, HashSet<int>> valuesPerNodeInRound;

        private bool isConsensReached = false;
        private int decisionResult;
        private bool isConsensusComputed = false;
        private bool isDecisionCorrect = true;

        protected bool roundDone = false;
        private bool isFinished = false;
        private bool isStarted = false;
        #endregion Attribute

        #region Eigenschaften
        /// <summary>
        /// Gibt und setzt (falls der Algorithmus nicht begonnen hat) den Wert des Knotens an.
        /// </summary>
        public int Value
        {
            get { return value; }
            set
            {
                if (IsStarted) throw new Exception("Der Wert kann nach Start des Algorithmus nicht mehr verändert werden!");
                this.valueSetBefore.Clear();
                this.valueSetAfter.Clear();
                this.value = value;
                this.valueSetBefore.Add(value);
                this.valueSetAfter.Add(value);
            }
        }

        /// <summary>
        /// Gibt true wenn der Algorithmus angefangen hat.
        /// </summary>
        public bool IsStarted { get { return round > 0 || roundDone || isFinished || isStarted || FailStops; } }

        /// <summary>
        /// Sende Event. (für Logging)
        /// </summary>
        public event MsgEvent MessageSent;

        /// <summary>
        /// Empfangs Event. (für Logging)
        /// </summary>
        public event MsgEvent MessageReceived;

        /// <summary>
        /// Empfangs Event. (für Logging)
        /// Wird im Fall einer erwarteten Nachricht die durch FailStop ausbleibt gerufen.
        /// </summary>
        public event MsgEvent NoMessageReceived;

        /// <summary>
        /// Wird geworfen wenn die Runde beendet wurde.
        /// </summary>
        public event SimpleEvent RoundFinished;

        /// <summary>
        /// Wird geworfen wenn die Entscheidungsregel angewendet wurde.
        /// </summary>
        public event SimpleEvent DecisionFinished;

        /// <summary>
        /// Gibt an ob Konsens erreicht wurde.
        /// </summary>
        public bool IsConsensReached { get { return isConsensReached; } }


        /// <summary>
        /// Gibt an ob Konsens versucht wurde zu erreichen.
        /// </summary>
        public bool IsConsensusComputed { get { return isConsensusComputed; } }

        /// <summary>
        /// Gibt die Entscheidungsmenge zurück.
        /// </summary>
        public int DecisionResultSet { get { return decisionResult; } }

        /// <summary>
        /// Gibt die letzte Menge zurück (ist NICHT die Entscheidung).
        /// </summary>
        public HashSet<int> LatestValues { get { return valueSetAfter; } }

        /// <summary>
        /// Gibt die letzte Menge zurück (ist NICHT die Entscheidung).
        /// </summary>
        public HashSet<int> LatestRoundValues { get { return valueSetBefore; } }

        /// <summary>
        /// Gibt alle Werte in allen Runden zurück.
        /// </summary>
        public Dictionary<string, HashSet<int>> ValuesPerRound { get { return valuesPerRound; } }

        /// <summary>
        /// Gibt alle Werte in der jetzigen Runde zurück.
        /// </summary>
        public Dictionary<string, HashSet<int>> ValuesPerNodeInRound { get { return this.valuesPerNodeInRound; } }


        /// <summary>
        /// Gibt an ob genug Runden durchlaufen wurden, um festzustellen ob die Entscheidung korrekt ist.
        /// </summary>
        public bool IsDecisionCorrect { get { return isDecisionCorrect; } }
        #endregion Eigenschaften

        #region Methoden
        /// <summary>
        /// Instanziiert und initialisert den Knoten.
        /// </summary>
        public Node()
            : base()
        {
            this.value = 0;
            this.valuesPerRound = new Dictionary<string, HashSet<int>>();
            this.valuesPerNodeInRound = new Dictionary<string, HashSet<int>>();
        }

        /// <summary>
        /// FloodSet spezifische Sendemethode, wobei die Nachricht für das spezifische Ziel erstellt wird.
        /// </summary>
        /// <param name="link"></param>
        /// <param name="message"></param>
        public override void Send(Link link, IMessage message)
        {
            // Event für das Versenden:
            if (message is Message && MessageSent != null) MessageSent(this, (Node)(link.SourceNode == this ? link.TargetNode : link.SourceNode), link, (Message)message);
            base.Send(link, message);
        }

        /// <summary>
        /// FloodSet-spezifische Broadcastmethode.
        /// "Nachrichten" aus der formalen Beschreibung.
        /// </summary>
        /// <param name="message">Eine Dummynachricht.</param>
        public override void Broadcast(IMessage message)
        {
            if (FailStops)
            {
                // Wenn der Knoten ausgefallen ist, macht er auch nichts...
                // Mit Chris absprechen...
                foreach (Link l in Links) ((Node)(l.SourceNode == this ? l.TargetNode : l.SourceNode)).Solve(l);
            }
            else
            {
                // An alle anderen wird die Menge W geschickt:
                base.Broadcast(new Message(valueSetBefore));

                // Event für das Senden/Empfangen:
                if (MessageSent != null) MessageSent(this, this, null, new Message(valueSetBefore));
                if (MessageReceived != null) MessageReceived(this, this, null, new Message(valueSetBefore));
            }

            isStarted = true;
            roundDone = true;
        }


        /// <summary>
        /// Teilt mit dass der Tick verstrichen ist und keine Nachricht angekommen ist.
        /// </summary>
        /// <param name="link">Der Link über den keine Übertragung stattgefunden hat.</param>
        internal void Solve(Link link)
        {
            if (!FailStops)
            {
                if (NoMessageReceived != null) NoMessageReceived((Node)(link.SourceNode == this ? link.TargetNode : link.SourceNode), this, link, null);
                isConsensReached = false;
                isFinished = false;
                isStarted = true;
            }
        }

        /// <summary>
        /// Schaltet zur nächsten Runde weiter.
        /// "Statusübergang" in der formalen Beschreibung.
        /// </summary>
        /// <param name="f">Fehlerhafte Prozesse</param>
        public void FinishRound(int f)
        {
            if (roundDone)
            {
                if (round <= f)
                {
                    roundDone = false;
                    round++;

                    if (!FailStops)
                    {
                        valueSetBefore = valueSetAfter;
                        valuesPerRound.Add(round.ToString(), valueSetBefore);
                        valuesPerNodeInRound.Clear();
                    }

                    if (round == f + 1)
                    {
                        isFinished = true;
                        decide(f);
                    }

                    // Event für das Beenden der Runde:
                    if (!FailStops && RoundFinished != null) RoundFinished(this);
                }
                else
                {
                    throw new Exception("Der Algorithmus wurde bereits beendet.");
                }
            }
            else
            {
                throw new Exception("Die Runde wurde noch nicht abgehandelt.");
            }
        }

        /// <summary>
        /// FloodSet spezifische Empfangsmethode
        /// </summary>
        /// <param name="link"></param>
        /// <param name="message"></param>
        internal override void Receive(Link link, IMessage message)
        {
            base.Receive(link, message);

            if (FailStops)
            {
                // kann keine Werte empfangen, wenn er ausgefallen ist.
            }
            else
            {
                // Alle Elemente zusammenfügen von der eigenen Menge W und der der Nachricht:
                valueSetAfter.UnionWith(((Message)message).Value);

                valuesPerNodeInRound.Add((((Node)(link.SourceNode == this ? link.TargetNode : link.SourceNode)).Id.ToString()), ((Message)message).Value);
            }

            // Event für das Empfangen:
            if (!FailStops && message is Message && MessageReceived != null) MessageReceived((Node)(link.SourceNode == this ? link.TargetNode : link.SourceNode), this, link, (Message)message);
            isConsensReached = false;
            isFinished = false;
            isStarted = true;
        }


        /// <summary>
        /// Führt die Entscheidungsregel auf den Baum aus.
        /// Kleiner if-Block aus "Statusübergang" in der formalen Beschreibung.
        /// </summary>
        /// <param name="f">Die maximal fehlerhaften Prozesse.</param>
        private void decide(int f)
        {
            if (FailStops)
            {
                // Keine Statusänderung...
            }
            else
            {
                if (valueSetAfter.Count == 1)
                {
                    decisionResult = valueSetAfter.First();
                    isConsensReached = true;
                }
                else
                {
                    decisionResult = value;
                    isConsensReached = false; // Der Algo ist damit zwar ganz normal abgelaufen, aber es gibt keinen Konsens unter allen Knoten.
                }


                // Prüfung ob der Algorithmus korrekt gewesen sein kann. Wurden genug Runden durchlaufen?
                int realFailed = this.FailStops ? 1 : 0; // wir gehen davon aus dass die Entscheidung NICHT atomar ist. (Ist sie aber.)
                foreach (Link l in Links) realFailed += ((Node)(l.SourceNode == this ? l.TargetNode : l.SourceNode)).FailStops ? 1 : 0;
                isDecisionCorrect = f >= realFailed;

                isConsensusComputed = true;
                // Event für die Entscheidungsfindung:
                if (DecisionFinished != null) DecisionFinished(this);
            }
        }
        #endregion Methoden
    }
}
